/*
 *  Copyright 2008-2010 www.jrails.org
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.rails.core.db;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.rails.core.model.ActiveRecord;
import org.rails.core.sql.SQLDeleteBuilder;
import org.rails.core.sql.SQLFactory;
import org.rails.core.sql.SQLInsertBuilder;
import org.rails.core.sql.SQLManager;
import org.rails.core.sql.SQLUpdateBuilder;
import org.rails.core.util.Utils;

final class StatementHelper {

	private final Logger logger = Logger.getLogger(StatementHelper.class);

	private PreparedStatement statement;

	private List<Object> params;

	private Connection connection;

	private String sql;

	private int maxResult = 0;

	public StatementHelper() {
		super();
	}

	public StatementHelper(Connection connection) {
		super();
		this.connection = connection;
	}

	public StatementHelper(Connection connection, String sql,
			List<Object> params) {
		super();
		this.connection = connection;
		this.sql = sql;
		this.params = params;
	}

	public int executeCreate(final ActiveRecord record) throws SQLException {
		if (record.getTable().isCall()) {
			statement = this.connection.prepareStatement(sql,
					new String[] {record.getTable().getPrimaryKey()});
			bindValues();
			final int r = statement.executeUpdate();
			final ResultSet rs = statement.getGeneratedKeys();
			while (rs.next()) {
				record.setId(rs.getObject(1));
			}
			return r;
		} else {
			statement = this.connection.prepareStatement(sql);
			bindValues();
			return statement.executeUpdate();
		}
	}

	public int executeUpdate() throws SQLException {
		statement = this.connection.prepareStatement(sql);
		bindValues();
		return statement.executeUpdate();
	}

	public int getMaxResult() {
		return maxResult;
	}

	public void setMaxRows(int maxResult) {
		this.maxResult = maxResult;
	}

	public int[] executeInsertBatch(List<? extends ActiveRecord> records) throws SQLException {
		boolean  b = true;		
		for (Iterator<? extends ActiveRecord> iterator = records.iterator(); iterator
				.hasNext();) {
			final ActiveRecord record = iterator.next();
			final SQLManager sqm = new SQLManager(record);
			sqm.setParameter(record);
			final SQLFactory sf = new SQLFactory(sqm);
			final SQLInsertBuilder sib = sf.getInsertBuilder();
			if(b){				
				statement = this.connection.prepareStatement(sib.getSql());
				logger.info("Batch SQL=> " + sib.getSql());
			}
			b = false;
			params = sib.getParams();
			logger.info("Add Batch => " + params);
			bindValues();
			statement.addBatch();
		}
		if(records.size() > 0)
			return statement.executeBatch();
		else
			return null;
	}

	public int[] executeUpdateBatch(List<? extends ActiveRecord> records) throws SQLException {		
		boolean b = true;
		for (Iterator<? extends ActiveRecord> iterator = records.iterator(); iterator
				.hasNext();) {
			final ActiveRecord record = iterator.next();
			final SQLManager sqm = new SQLManager(record);
			sqm.addCondition(Utils.format("{0} = :{0}", record.getTable()
					.getPrimaryKey()));
			final SQLFactory sf = new SQLFactory(sqm);
			final SQLUpdateBuilder sub = sf.getUpdateBuilder();
			if(b){
				statement = this.connection.prepareStatement(sub.getSql());
				logger.info("Batch SQL => " + sub.getSql());
			}
			b = false;
			params = sub.getParams();
			logger.info("Add Batch => " + params);
			bindValues();
			statement.addBatch();
		}
		if(records.size() > 0)
			return statement.executeBatch();
		else
			return null;
	}
	
	public int[] executeDeleteBatch(List<? extends ActiveRecord> records) throws SQLException {
		boolean b = true;
		for (Iterator<? extends ActiveRecord> iterator = records.iterator(); iterator
				.hasNext();) {
			final ActiveRecord record = iterator.next();
			final SQLManager sqm = new SQLManager(record);
			sqm.addCondition(Utils.format("{0} = :{0}", record.getTable()
					.getPrimaryKey()));
			final SQLFactory sf = new SQLFactory(sqm);
			final SQLDeleteBuilder sdb = sf.getDeleteBuilder();
			if(b){
				statement = this.connection.prepareStatement(sdb.getSql());
				logger.info("Batch SQL=> " + sdb.getSql());
			}
			b = false;
			params = sdb.getParams();
			logger.info("Add Batch => " + params);
			bindValues();
			statement.addBatch();
		}
		if(records.size() > 0)
			return statement.executeBatch();
		else
			return null;
	}

	public List<Map<String, Object>> executeQuery() throws SQLException {
		Map<String, Object> row = null;
		final List<Map<String, Object>> rows = new ArrayList<Map<String, Object>>();
		statement = this.connection.prepareStatement(sql);
		statement.setMaxRows(maxResult);
		bindValues();
		final ResultSet result = statement.executeQuery();
		final ResultSetMetaData rsmd = result.getMetaData();
		final int c = rsmd.getColumnCount();
		while (result.next()) {
			row = new HashMap<String, Object>();
			for (int i = 0; i < c; i++) {
				row.put((rsmd.getColumnName(i + 1)).toLowerCase(), result
						.getObject(i + 1));
			}
			rows.add(row);
		}
		result.close();
		return rows;
	}

	public List<String> getColumns() throws SQLException {
		final List<String> list = new ArrayList<String>();
		statement = this.connection.prepareStatement(sql);
		final ResultSet result = statement.executeQuery();
		final ResultSetMetaData rsmd = result.getMetaData();
		for (int i = 0; i < rsmd.getColumnCount(); i++) {
			list.add(rsmd.getColumnName(i + 1).toLowerCase().trim());
		}
		result.close();
		return list;
	}
	
	public List<String[]> getColumnMaps() throws SQLException {
		final List<String[]> maps = new ArrayList<String[]>();
		statement = this.connection.prepareStatement(sql);
		final ResultSet result = statement.executeQuery();
		final ResultSetMetaData rsmd = result.getMetaData();
		for (int i = 0; i < rsmd.getColumnCount(); i++) {
			final String[] cls = new String[]{rsmd.getColumnName(i + 1).toLowerCase().trim(),
					rsmd.getColumnTypeName(i + 1),rsmd.getColumnClassName(i + 1)};
			maps.add(cls);
		}
		result.close();
		return maps;
	}

	public void close() throws SQLException {
		if (statement != null)
			statement.close();
	}

	private void bindValues() throws SQLException {
		for (int i = 0; i < params.size(); i++) {
			if (params.get(i) != null)
				statement.setObject(i + 1, params.get(i));
			else
				statement.setNull(i + 1, Types.VARCHAR);
		}
	}
	
}
